/* shell.c, Ait, BSD 3-Clause, Kevin Bloom, 2023 */

#include <stdio.h>
#include "header.h"
#include "termbox.h"

char opcmdtext[STRBUF_M];
char ipcmdtext[STRBUF_M];

/* TODO: make this generic
   M-e will display "Shell Command: " in the msgline. You then input the command
   you want.
   Eventually maybe make it so that there are different types of commands:
   - input, inputs something at the point
   - open, runs a command and ait will open the output (this currently works)
   - region/replace, use the region as the input to the shell cmd and then
     replace the region with the output
   - new buffer, runs the command and the output is placed in a new buffer

   I probably would want some keybinds to certain commands, however.
   Also, I'd like to make it so that if you have a region selected, it can be
   executed much like how acme does it.

   io = Insert = 0, Open = 1
*/
void get_popen_data(int io) {
  FILE *pf;
  char *command = NULL;
  char *data = NULL;
  buffer_t *bp;
  char *insertp = "Shell Command", *openp = "Open Via";
  char prompt[STRBUF_M + 12 + strlen(insertp)];
  char str_line[10];
  int cpos = 0, done = FALSE;
  int c, hasregion = FALSE, cmdsize = 0, escaped_size = 0;
  int start_col = strlen(prompt);
  int i = 0, k = 0, isLineNumber = 0, line, l = 0, onscrap = 0;
  struct tb_event ev;
  char cmdtext[STRBUF_M];
  cmdtext[0] = '\0';
  point_t point;
  char_t *oscrap = NULL, *escaped_region = NULL;

  if(io) {
    sprintf(prompt, "%s", openp);
    if(opcmdtext[0] != '\0') {
      strcat(prompt, " (default ");
      strcat(prompt, opcmdtext);
      strcat(prompt, ")");
    }
  } else {
    sprintf(prompt, "%s", insertp);
    if(ipcmdtext[0] != '\0') {
      strcat(prompt, " (default ");
      strcat(prompt, ipcmdtext);
      strcat(prompt, ")");
    }
  }

  strcat(prompt, ": ");

  start_col = strlen(prompt);

  display_prompt_and_response(prompt, cmdtext);
  cpos = strlen(cmdtext);

  for (;;) {
    tb_present();
    if(tb_poll_event(&ev) != TB_OK) return;

    if(!ev.mod)
      c = ev.ch;
    else
      c = ev.key;

    /* ignore control keys other than return, C-g, backspace, CR,  C-s, C-R, ESC */
      if (c < 32 &&
        c != TB_KEY_CTRL_G &&
        c != TB_KEY_BACKSPACE &&
        c != TB_KEY_BACKSPACE2 &&
       c != TB_KEY_ENTER &&
        c != TB_KEY_ESC)
      continue;

    switch(c) {
    case TB_KEY_ENTER: /* return */
      done = TRUE;
      break;

    case TB_KEY_ESC: /* esc */
    case TB_KEY_CTRL_G: /* ctrl-g */
      tb_set_cursor(0, MSGLINE);
      clrtoeol("", MSGLINE);
      return;

    case TB_KEY_BACKSPACE2: /* del, erase */
    case TB_KEY_BACKSPACE: /* backspace */
      if (cpos == 0)
        continue;
      cmdtext[--cpos] = '\0';
      tb_set_cursor(start_col + cpos, MSGLINE);
      display_prompt_and_response(prompt, cmdtext);
      break;

    default:
      if (cpos < STRBUF_M - 1) {
        tb_set_cursor(start_col + cpos, MSGLINE);
        cmdtext[cpos++] = c;
        cmdtext[cpos] = '\0';
        addch(c);
      }
      break;
    }
    if(done)
      break;
  }

  if(cmdtext[0] == '\0') {
    if(io && opcmdtext[0] != '\0')
      strncpy(cmdtext, opcmdtext, STRBUF_M);
    else if(!io && ipcmdtext[0] != '\0')
      strncpy(cmdtext, ipcmdtext, STRBUF_M);
    else
      return;
  }

  if(io)
    strncpy(opcmdtext, cmdtext, STRBUF_M);
  else
    strncpy(ipcmdtext, cmdtext, STRBUF_M);

  if (curbp->b_mark != NOMARK && curbp->b_point != curbp->b_mark) {
    oscrap = (char_t*) malloc(nscrap);
    onscrap = nscrap;
    (void) memcpy(oscrap, scrap, nscrap * sizeof (char_t));
    copy_cut(TRUE, TRUE, FALSE);
    hasregion = TRUE;
  } else {
    undoset(5, FALSE);
  }

  strcpy(temp, editor_dir);

  tb_shutdown();

  if(hasregion) {
    /* Find all dollar signs and increase the size by one for each sign. */
    for(int i = 0; scrap[i] != '\0'; i++) {
      if(scrap[i] == '$' || scrap[i] == '`')
        escaped_size += 2;
      else
        escaped_size++;
    }
    escaped_region = malloc(sizeof(char_t *)*escaped_size);
    /* Escape all $ with \$. This prevents the echo command from trying to
       do a variable substitution.
    */
    for(int i = 0, k = 0; scrap[i] != '\0'; i++, k++) {
      if(scrap[i] == '$' || scrap[i] == '`') {
        escaped_region[k] = '\\';
        k++;
        escaped_region[k] = scrap[i];
      } else {
        escaped_region[k] = scrap[i];
      }
    }
    cmdsize = 6*4*escaped_size*strlen(cmdtext);
    command = malloc(sizeof(char *)*cmdsize);
    strcpy(command, "echo \"");
    strcat(command, (char *)escaped_region);
    strcat(command, "\" | ");
    strcat(command, cmdtext);
  } else {
    cmdsize = strlen(cmdtext);
    command = malloc(sizeof(char *)*cmdsize);
    strcpy(command, cmdtext);
  }

  // Setup our pipe for reading and execute our command.
  pf = popen(command,"r");

  if(pf == NULL){
    msg("Could not open pipe for output.");
    return;
  }

  data = malloc(sizeof(char *) * 512 * 3);
  fgets(data, sizeof(char *) * 512 * 3, pf);

  tb_init();
  LINES = tb_height();
  COLS = tb_width();
  MSGLINE = LINES-1;
  tb_set_input_mode(TB_INPUT_ALT);

  /* Mark the log for update */
  redraw();

  /* check if canceled command */
  if(data[0] == -1 || data[0] == 0) {
    if(io == 0) {
      /* put the original contents back in the buffer and reset scrap */
      paste_internal(TRUE);
      free(scrap);
      nscrap = onscrap;
      scrap = (char_t*) malloc(nscrap);
      (void) memcpy(scrap, oscrap, nscrap * sizeof (char_t));
    }
  } else {
    switch(io) {
      case 0: {
        /* TODO: don't do this with many insert_str calls
           make it into one big string and insert it at once.
        */
        input = (char_t *)data;
        insert_str();
        while(fgets(data, sizeof(char *)*512*3, pf) != NULL) {
          input = (char_t *)data;
          insert_str();
        }
        if (curbp->b_point >= curbp->b_epage)
          curbp->b_reframe = 1;
        if(nscrap > 0) {
          curbp->b_line++;
          lastcommand = KBD_DELETE_CHAR;
          backsp();
        }
        if(oscrap != NULL)
          free(oscrap);
        break;
      }
      case 1: {
        data[strlen(data)-1] = '\0';
        /* Find the file name and find the line number */
        while(data[i] != '\0') {
          if(isLineNumber) {
            str_line[k] = data[i];
            k++;
          } else if(data[i] != ':') {
            temp[strlen(editor_dir)+i] = data[i];
          } else {
            isLineNumber = TRUE;
            l = i;
          }
          i++;
        }
        if(!isLineNumber)
          l = i;
        temp[strlen(editor_dir)+l] = '\0';
        bp = find_buffer(temp, TRUE);
        disassociate_b(curwp);
        curbp = bp;
        associate_b2w(curbp, curwp);
        /* load the file if not already loaded */
        if (bp != NULL && bp->b_fname[0] == '\0') {
           if (!load_file(temp)) {
             msg("New file %s", temp);
           }
          strncpy(curbp->b_fname, temp, NAME_MAX);
          curbp->b_fname[NAME_MAX] = '\0'; /* truncate if required */
          if(isLineNumber) {
            line = atoi(str_line);
            point = line_to_point(line);
            if (point != -1) {
              curbp->b_point = point;
              if (curbp->b_epage < pos(curbp, curbp->b_ebuf))
                curbp->b_reframe = 1;
              msg("Line %d", line);
            } else {
              msg("Line %d, not found", line);
            }
            update_display(TRUE);
          }
        }
        break;
      }
    }
  }
  if(command != NULL)
    free(command);
  if(data != NULL)
    free(data);
  if (pclose(pf) != 0)
    msg("Error: Failed to close command stream.");
}
